iT邦幫忙

2022 iThome 鐵人賽

DAY 5
1

舊觀念組成新結構,這些結構形成新觀念,新觀念不斷復合,並無休止地持續下去,越來越遠離每個人的最原始的本質。這就是人類思維的運作方式。

Douglas R. Hofstadter

說到class。大概第一個念頭就是物件導向程式設計。畢竟,OOP的根源即源於class的概念。class是創建實例instance的藍圖。大體來說,它們封裝了數據與程式碼以操作改變(mutate)數據。

在JS中,class建立於原型prototype之上,並用作創建實例的模板。它們(就像在任何其他語言中一樣)本質上是命令式的(imperative)。

如此一來,class可謂與FP的原則背道而馳,所以在大多數情況下,我們盡量也應該避免使用class。但不得不承認,有些時候,class也有最佳的使用時機與意義(例如昨天的例子)。

每當使用class時,問問自己是否尤其必要性,是否可以用declarative的方式表達,是否只用function就可以實現。

依照我老闆的話,九成的情況下,是可以的!

現在就讓我們一步步重構class以達成declarative的風格。

初階版本:class與實例

以下這個classUserBuilder主要會格式化使用者名稱formatting()與打招呼greeting()

// 可能會被FP老闆討厭的寫法

// class
class UserBuilder {
    constructor(userName) {
        this.userName = userName;
    }
    
    formatting() {
        this.userName = this.userName
            .split(' ')
            .map(s => s.charAt(0).toUpperCase() + s.substring(1))
            .join(' ');
    }
    
    greeting() {
        this.userName = `Welcome to Ryvn's home, ${this.userName}!`;
    }
    
    get changedName() {
        return this.userName;
    }
}

// main process
const user = new UserBuilder('mira austen');
user.formatting();
user.greeting();
const newUserName = user.changedName;

console.log(newUserName);
// Welcome to Ryvn's home, Mira Austen!

FP老闆的點評:

  1. 優點:這個例子創建一個class封裝formatting()greeting()的邏輯,因此使用該class的人可以不必在乎實現細節和過程中的每個步驟。也就是達成了封裝encapsulation的目的。
  2. 缺點:在這個例子中,創造實例實在沒有必要,並且有改變數據狀態的過程。也就是説,使用new關鍵字創造UserBuilder的實例這個行為沒有為某種我們需要的目的或需求服務。

修正版本:classstatic

不牽扯instance最直覺的想法就是使用靜態方法static method。因此,或許可以改成下面形式:

// 可能沒這麼討厭的寫法

// class
class UserBuilder {
    static formatting(userName) {
        return userName
            .split(' ')
            .map(s => s.charAt(0).toUpperCase() + s.substring(1))
            .join(' ');
    }
    
    static greeting(userName) {
        return `Welcome to Ryvn's home, ${userName}!`;
    }
}

// main process
const formattedUserName = UserBuilder.formatting('mira austen');
const greetingString = UserBuilder.greeting(formattedUserName);

console.log(greetingString);
// Welcome to Ryvn's home, Mira Austen!

FP老闆的點評:

  1. 優點:這個例子使用靜態方法,讓class擔任起命名空間的作用。這裡沒有實例,也沒有改變資料狀態。更重要的是,這裡的方法的返回值都是決定性的(deterministic)基於其輸入。滿足FP中pure的原則。
  2. 缺點:class存在似乎沒有其必要性。亦或是說,其當前意義可以透過別的方式實現。

最終版本:functionexport/import

為了完全去除class,我們可以使用functionexport/import達成相同目的。

首先,在userbuilder.js中,我們可以這樣寫:

// 不太被討厭的寫法

export function formatting(userName) {
        return userName
            .split(' ')
            .map(s => s.charAt(0).toUpperCase() + s.substring(1))
            .join(' ');
    }
    
export function greeting(userName) {
        return `Welcome to Ryvn's home, ${userName}!`;
    }

而在使用之處,則:

// 不太被討厭的寫法

import { formatting, greeting } from "./userbuilder";

const formattedUserName = formatting('mira austen');
const greetingString = greeting(formattedUserName);

console.log(greetingString);
// Welcome to Ryvn's home, Mira Austen!

現在,我們的程序完全是declarative的,這不僅讓我們code更容易測試,容易debug,並且是treeshakable(藉由例如Webpack或Rollup排除沒用到的utilities,以便獲得更小的bundle size)。更重要的是,可能會讓FP老闆開心。


上一篇
摯友:Array(2/2)
下一篇
蜜糖毒藥:再見Functional Programming
系列文
被討厭的前端實務手冊|JS x TS x React18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言